#include "core\sampler.h"
#include "sampler\random.h"
#include "sampler\lowdiscrepancy.h"
#include "sampler\poissondisk.h"
#include "sampler\stratified.h"
#include "sampler\halton.h"
#include "io.h"
#include <gl\GLUT.h>

typedef enum {
	RANDOM = 0,
	STRATIFIED,
	LOWDISCREPANCY,
	HALTON,
	POISSONDISK
} SAMPLERTYPE;

typedef enum {
	IMAGE = 0,
	LENS,
	TIME
} SAMPLECOMPONENT;

string samplerName[] = {"random", "stratified", "lowdiscrepancy", "halton", "poisson-disk"};

// global variables
int width = 512, height = 512;
int xPixelSamples = 4, yPixelSamples = 4;
int N = xPixelSamples * yPixelSamples;
Sample *oneSample = NULL;
Sample *samples = NULL;
Sampler *sampler = NULL;
static bool generateSamples = true;
bool ifDrawGrid = true;
bool samplerChanged = true;
SAMPLERTYPE type = RANDOM;
int xPixelStart = 0, xPixelEnd = 1, yPixelStart = 0, yPixelEnd = 1;
bool needLensSamples = true, needTimeSamples = true;
SAMPLECOMPONENT drawComponent = IMAGE;

string componentName[] = {"imagesamples", "lenssamples", "timesamples"};

bool initSampler() {
	// create a new sampler
	SAFE_DELETE(sampler);
	switch (type) {
		case 0:
			sampler = new RandomSampler(xPixelStart, xPixelEnd, yPixelStart, yPixelEnd, N, needLensSamples, needTimeSamples);
			break;
		case 1:
			sampler = new StratifiedSampelr(xPixelStart, xPixelEnd, yPixelStart, yPixelEnd, xPixelSamples, yPixelSamples, needLensSamples, needTimeSamples);
			break;
		case 2:
			sampler = new LDSampler(xPixelStart, xPixelEnd, yPixelStart, yPixelEnd, N, needLensSamples, needTimeSamples);
			break;
		case 3:
			sampler = new HaltonSampler(xPixelStart, xPixelEnd, yPixelStart, yPixelEnd, N, needLensSamples, needTimeSamples);
			break;
		case 4:
			sampler = new PDSampler(xPixelStart, xPixelEnd, yPixelStart, yPixelEnd, N, needLensSamples, needTimeSamples);
			break;
		default:
			Error("Unable to find sampler type\n");
			return false;
	}
	Debug("%s sampler was created\n", samplerName[type].c_str());
	// create a sample, from which we can duplicate N samples
	SAFE_DELETE(oneSample);
	oneSample = new Sample(sampler, NULL, NULL, NULL);

	// Get all samples from sampler
	if(1) {
		// 1. free memory of last samples
		SAFE_DELETE_ARRAY(samples);
		// 2. allocate momory for new samples
		if(type != POISSONDISK)
			samples = oneSample->Duplicate(N);
		else
			samples = oneSample->Duplicate(2*N);
		int sampleCount;
		int index = 0;
		// get more samples into samples start with index
		while(sampleCount = sampler->GetMoreSamples(samples+index))
			index += sampleCount;
		//Assert(index == N);
	}

	return true;
}

void drawSamples() {
	
	// Initialize sampler and samples
	if(samplerChanged) {
		initSampler();
		samplerChanged = false;
	}

	// select samples to draw
	int which = 0;
	switch (drawComponent) {
		case 0:
			which = 0;
			break;
		case 1:
			if(needLensSamples)
				which = 1;
			else {
				Warning("No lensSamples, draw Image samples instead\n");
				which = 0;
				drawComponent = IMAGE;
			}
			break;
		case 2:
			if(needTimeSamples)
				which = 2;
			else {
				Warning("No timeSamples, draw Image samples instead\n");
				which = 0;
				drawComponent = IMAGE;
			}
			break;
		default:
			which = 0;
	}
	// draw samples
	
	glPointSize(1.);

	switch (which) {
		case 0:
			
			glColor3f(1.,0.,0.);
			glBegin(GL_POINTS);
			for(int i = 0; i < N; i++)
				glVertex2f(samples[i].imageSample.x , samples[i].imageSample.y);
			glEnd();
			
			break;
		case 1:
			
			glColor3f(0.,1.,0.);
			glBegin(GL_POINTS);
			for(int i = 0; i < N; i++)
				glVertex2f(samples[i].lensSample.x, samples[i].lensSample.y);
			glEnd();

			break;

		case 2:
			glColor3f(0.,0.,1.);

			glBegin(GL_POINTS);
			// draw time samples along diagonal
			for(int i = 0; i < N; i++)
				glVertex2f(samples[i].time, samples[i].time);
			glEnd();
			
			break;
	}
}

void clearUp() {
	SAFE_DELETE(sampler);
	SAFE_DELETE(oneSample);
	SAFE_DELETE_ARRAY(samples);
}

void init() {

	glClearColor( .0, .0, .0, 0.0);  //Set the clear color to black

	// Specify the domain of the viewing window
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	// The para are: (left, right, bottom, top)
	gluOrtho2D(.0, 1.0, .0, 1.0); 
	glMatrixMode(GL_MODELVIEW);
	glViewport(0, 0, width, height);

	// Here align to 1 byte, default is 4 bytes
	// for efficiency, it's better to use 4 or 8 bytes for alignment
	// glReadPixels/glWritePixels depend on these settings
	glPixelStorei(GL_PACK_ALIGNMENT, 1);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
}
void drawGrid() {
	// draw grid
	float invX = 1.f / xPixelSamples;
	float invY = 1.f / yPixelSamples;

	//gray grid line
	glColor3f(0.5, 0.5, 0.5f);
	
	glBegin(GL_LINES);
		
	for(int i = 1; i < xPixelSamples; i++) {
		glVertex2f(i*invX, 0.f);
		glVertex2f(i*invX, 1.f);
	}
	for(int i = 1; i < yPixelSamples; i++) {
		glVertex2f(0.f, i*invY);
		glVertex2f(1.f, i*invY);
	}

	glEnd();
}
void display() {
	
	glClear(GL_COLOR_BUFFER_BIT);
	
	// draw grid
	if(ifDrawGrid)
		drawGrid();

	// draw samples
	drawSamples();

	glutSwapBuffers();
	//glFlush();
}
void idle() {
	// do nothing
	display();
}
void reShape(int w, int h)
{
	width = w;
	height = h;
	glMatrixMode(GL_MODELVIEW);
	glViewport(0, 0, width, height);
	
	glutPostRedisplay();
}
void keyboard(unsigned char key, int x, int y) {
	switch (key) {
		case 27:
		case 'q':
		case 'Q':
			exit(0);
			break;
		case 'd':
		case 'D':
			break;
		case 's':
		case 'S':
			char name[128];
			sprintf(name, "%s_%s_%s_%d.bmp\0", samplerName[type].c_str(), componentName[drawComponent].c_str(), ifDrawGrid ? "grid" : "nogrid", N);
			SaveResult(width, height, name);
			break;
		case 'g':
		case 'G':
			ifDrawGrid = !ifDrawGrid;
			break;
		case '1':
			drawComponent = IMAGE;
			break;
		case '2':
			drawComponent = LENS;
			break;
		case '3':
			drawComponent = TIME;
			break;
		case '[':
			type = SAMPLERTYPE((((int)type)-1 + 5) % 5);
			samplerChanged = true;
			break;
		case ']':
			type = SAMPLERTYPE((((int)type)+1) % 5);
			samplerChanged = true;
			break;
		case '-':
		case '_':
			if(xPixelSamples != 1) {
				xPixelSamples /= 2;
				yPixelSamples /= 2;
				xPixelSamples = max(xPixelSamples, 1);
				yPixelSamples = max(yPixelSamples, 1);
				N = xPixelSamples * yPixelSamples;
				samplerChanged = true; 
			}
			break;
		case '=':
		case '+':
			if(xPixelSamples != 64) {
				xPixelSamples *= 2;
				yPixelSamples *= 2;
				xPixelSamples = min(xPixelSamples, 64);
				yPixelSamples = min(yPixelSamples, 64);
				N = xPixelSamples * yPixelSamples;
				samplerChanged = true;
			}
			break;
		default:
			// do nothing
			break;
	}
}
void help() {
	printf("Sampler Viewer - by Li Xiaosheng\n");
	printf("* Available sampler:\n");
	printf("  -1. Random\n");
	printf("  -2. Stratified\n");
	printf("  -3. Low-discrepancy\n");
	printf("  -4. Halton\n");
	printf("  -5. Poisson-disk\n");
	printf("* Key board :\n");
	printf("  - 1 display image samples\n");
	printf("  - 2 display lens samples\n");
	printf("  - 3 display time samples\n");
	printf("  - [ previous sampler\n");
	printf("  - ] next sampler\n");
	printf("  - + increase samples\n");
	printf("  - - decrease samples\n");
	printf("  - g/G toggle grid\n");
	printf("  - s save result and its fourier transform\n");
	printf("  - ESC/q quit\n");
	printf("\n");
}
int main(int argc, char* argv[]) {

	// Help 
	help();

	// Initialize GLUT function callings
	glutInit( &argc, argv);

	// Initialize GLUT display mode
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);

	// Set window size (width, height) in number of pixels  	
	glutInitWindowSize( width, height);   

	// Specify window position, from the left and top of the screen, in numbers of pixels 
	glutInitWindowPosition( 200, 100);        

	// Specify a window creation event 
	glutCreateWindow( "Samples Viewer"); 

	//Specify the drawing function, called when the window is created or re-drew 

	glutDisplayFunc( display);     
	glutKeyboardFunc(keyboard);
	glutIdleFunc(idle);
	glutReshapeFunc(reShape);

	init();				// Initializaton for openGL

	glutMainLoop();		// Enter the event processing loop

	clearUp();			// Clear Up, free memory

	return 0;			// ANSI C requires main() to return an int
}